package bancosys.tec.services.implementor.descriptor;

import java.lang.annotation.Annotation;
import java.util.Set;

import jmine.tec.di.annotation.Injected;
import jmine.tec.services.annotations.AllProperties;
import jmine.tec.services.annotations.Input;
import jmine.tec.services.annotations.Parameter;
import jmine.tec.services.annotations.ServiceImplementor;
import jmine.tec.services.annotations.SuffixOf;
import jmine.tec.services.annotations.WithPreffix;
import jmine.tec.services.annotations.WithRegex;
import jmine.tec.utils.collection.impl.SmallSet;

/**
 * Superclasse para as classes que querem fazer o 'parsing' das anotacoes de servico. É package-private e depende de
 * {@link ServiceRuntimeImpl}.
 */
public class AbstractServiceAnnotationParser {

    private final ServiceRuntimeImpl runtime;

    private static final Set<Class<? extends Annotation>> PARAM_ANNOTATIONS = new SmallSet<Class<? extends Annotation>>();

    static {
        PARAM_ANNOTATIONS.add(Injected.class);
        PARAM_ANNOTATIONS.add(Input.class);
        PARAM_ANNOTATIONS.add(Parameter.class);
        PARAM_ANNOTATIONS.add(WithPreffix.class);
        PARAM_ANNOTATIONS.add(WithRegex.class);
        PARAM_ANNOTATIONS.add(AllProperties.class);
        PARAM_ANNOTATIONS.add(SuffixOf.class);
    }

    /**
     * C'tor
     * 
     * @param runtime {@link ServiceRuntimeImpl}
     */
    public AbstractServiceAnnotationParser(ServiceRuntimeImpl runtime) {
        super();
        this.runtime = runtime;
    }

    /**
     * Do array de anotacoes passado, procura um que esta no set passado. Lanca uma excecao se mais de uma anotacao esta presente. devolve
     * <code>null</code> se nenhum esta presente.
     * 
     * @param annotations o array de anotacoes
     * @param set o conjunto
     * @return {@link Annotation} desejada
     */
    protected final Annotation findOneAnnotation(Annotation[] annotations, Set<Class<? extends Annotation>> set) {
        Annotation desired = null;
        for (Annotation annotation : annotations) {
            if (set.contains(annotation.annotationType())) {
                if (desired != null) {
                    throw new IllegalArgumentException();
                }
                desired = annotation;
            }
        }
        return desired;
    }

    /**
     * @return {@link ServiceRuntimeImpl}
     */
    public final ServiceRuntimeImpl getRuntime() {
        return this.runtime;
    }

    /**
     * Coleta parametros de injecao. Usado para separar as anotacoes de metodos e construtores.
     * 
     * @param parameterAnnotations as anotacoes dos parametros
     * @return array de {@link Annotation}
     */
    protected final Annotation[] collectParameterInjectionAnnotations(final Annotation[][] parameterAnnotations) {
        Annotation[] collected = new Annotation[parameterAnnotations.length];
        int i = 0;
        for (Annotation[] annotations : parameterAnnotations) {
            collected[i++] = this.findOneAnnotation(annotations, PARAM_ANNOTATIONS);
        }
        return collected;
    }

    /**
     * Verifica se uma anotacao eh uma anotacao valida para parametros de metodos e/ou construtores.
     * 
     * @param ann {@link Annotation}
     * @return boolean
     */
    public static boolean isParameterAnnotation(Annotation ann) {
        return PARAM_ANNOTATIONS.contains(ann.annotationType());
    }

    /**
     * Dada a hierarquia de classes comecando por originalType, procura o tipo que declara a anotacao {@link ServiceImplementor}
     * 
     * @param originalType o tipo
     * @return {@link Class} ou null
     */
    public static Class<?> findTypeWithAnnotation(Class<?> originalType) {
        if (originalType == null) {
            return null;
        }
        if (originalType.getAnnotation(ServiceImplementor.class) != null) {
            return originalType;
        }
        return findTypeWithAnnotation(originalType.getSuperclass());
    }

    /**
     * @param parameterAnnotations os parametros
     * @return boolean
     */
    public static boolean isValidInjectionParameter(Annotation[][] parameterAnnotations) {
        boolean found;
        for (Annotation[] annotations : parameterAnnotations) {
            found = false;
            for (int i = 0; !found && i < annotations.length; i++) {
                Annotation annotation = annotations[i];
                found = isParameterAnnotation(annotation);
            }
            if (!found) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param type {@link Class}
     * @return boolean
     */
    protected final boolean isStateless(Class<?> type) {
        return findTypeWithAnnotation(type).getAnnotation(ServiceImplementor.class).stateless();
    }

}
